/*******************************************************************************
 * Copyright (c) 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bartosz Michalik (bartosz.michalik@gmail.com)
 *******************************************************************************/
package org.eclipse.pde.internal.core.product;

import java.io.PrintWriter;
import java.util.*;
import org.eclipse.pde.core.IModelChangedEvent;
import org.eclipse.pde.core.IModelChangedListener;
import org.eclipse.pde.core.plugin.*;
import org.eclipse.pde.internal.core.iproduct.*;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Class manages transformations
 * 
 * @author Bartosz Michalik
 * 
 */
public class TransformationsManager extends ProductObject implements ITransformationsManager, IModelChangedListener {

	private static final long serialVersionUID = 1L;
	private Map fBundleToTransforms;
	private Map fBundleToAffected;
	private boolean fNotify;

	public TransformationsManager(IProductModel model) {
		super(model);
		model.addModelChangedListener(this);
		fBundleToTransforms = new HashMap();
		fBundleToAffected = new HashMap();
		fNotify = true;
	}

	public boolean add(IProductTransform transform) {
		String pluginId = getPluginId((IPluginObject) transform.getTransformedObject());
		List transforms = (List) fBundleToTransforms.get(pluginId);
		if (transforms == null) {
			transforms = new LinkedList();
			fBundleToTransforms.put(pluginId, transforms);
		}
		transforms.remove(transform);
		boolean result = transforms.add(transform);
		if (result) {
			List overlapped = new ArrayList(transforms.size());
			if (transform instanceof RemoveTransform) {
				List affected = (List) fBundleToAffected.get(pluginId);
				if (affected == null) {
					affected = new LinkedList();
					fBundleToAffected.put(pluginId, affected);
				}
				affected.add(transform.getXPath());
				// remove all overlapped
				for (Iterator i = transforms.iterator(); i.hasNext();) {
					IProductTransform t = (IProductTransform) i.next();
					if (t == transform)
						continue;
					if (t.getXPath().indexOf(transform.getXPath()) != -1) {
						overlapped.add(t);
						i.remove();
						affected.remove(t.getXPath());
					}
				}

			}
			if (fNotify) {
				if (!overlapped.isEmpty()) {
					fireStructureChanged((IProductTransform[]) overlapped.toArray(new IProductTransform[transforms.size()]), IModelChangedEvent.REMOVE);
				}
				fireStructureChanged(transform, IModelChangedEvent.INSERT);
			}
		}
		return result;
	}

	public boolean remove(IProductTransform transform) {
		String pluginId = getPluginId((IPluginObject) transform.getTransformedObject());
		List transforms = (List) fBundleToTransforms.get(pluginId);
		boolean result = false;
		if (transforms != null) {
			result = transforms.remove(transform);
		}
		if (result) {
			if (transform instanceof RemoveTransform) {
				List affected = (List) fBundleToAffected.get(pluginId);
				if (affected != null) {
					affected.remove(transform.getXPath());
				}
			}
			if (fNotify) {
				fireStructureChanged(transform, IModelChangedEvent.REMOVE);
			}
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.pde.internal.core.iproduct.ITransformationsManager#remove
	 * (org.eclipse.pde.core.plugin.IPluginObject)
	 */
	public boolean remove(IPluginObject object) {
		if (!isManagedObject(object))
			return false;
		List transforms = (List) fBundleToTransforms.get(getPluginId(object));
		if (transforms != null) {
			for (Iterator i = transforms.iterator(); i.hasNext();) {
				IProductTransform transform = (IProductTransform) i.next();
				//TODO optimize if condition
				if (transform.getXPath().equals(ProductTransformFactory.toXPath(object, true))) {
					return remove(transform);
				}
			}
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.pde.internal.core.iproduct.ITransformationsManager#removeAll
	 * (java.lang.String)
	 */
	public IProductTransform[] removeAll(String pluginId) {
		IProductTransform[] result = new IProductTransform[0];
		List transforms = (List) fBundleToTransforms.remove(pluginId);
		fBundleToAffected.remove(pluginId);
		if (transforms != null) {
			result = (IProductTransform[]) transforms.toArray(new IProductTransform[transforms.size()]);
			if (result.length > 0 && fNotify) {
				fireStructureChanged(result, IModelChangedEvent.REMOVE);
			}
		}
		return result;
	}

	public boolean isAffected(IPluginObject object) {
		if (!isManagedObject(object))
			return false;
		List affected = (List) fBundleToAffected.get(getPluginId(object));
		if (affected == null)
			return false;
		for (Iterator i = affected.iterator(); i.hasNext();) {
			String elem = (String) i.next();
			if (ProductTransformFactory.toXPath(object, true).indexOf(elem) != -1)
				return true;
		}
		return false;
	}

	private String getPluginId(IPluginObject element) {
		return element.getPluginBase().getId();
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.pde.internal.core.product.ITransformationsManager#getTransforms
	 * ()
	 */
	public Map getTransforms() {
		return fBundleToTransforms;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.pde.internal.core.product.ITransformationsManager#isTransformed
	 * (org.eclipse.pde.core.plugin.IPluginObject)
	 */
	public boolean isTransformed(IPluginObject object) {
		if (!isManagedObject(object))
			return false;
		List transforms = (List) fBundleToTransforms.get(getPluginId(object));
		if (transforms != null) {
			for (Iterator i = transforms.iterator(); i.hasNext();) {
				IProductTransform transform = (IProductTransform) i.next();
				//TODO optimize if condition
				if (transform.getXPath().equals(ProductTransformFactory.toXPath(object, true)))
					return true;
			}
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.pde.internal.core.iproduct.ITransformationsManager#hasTransforms(org.eclipse.pde.core.plugin.IPluginObject)
	 */
	public boolean hasTransforms(IPluginObject object) {
		return getTransformFor(object) != null;
	}

	private IProductTransform getTransformFor(IPluginObject object) {
		if (object == null)
			return null;
		List transforms = (List) fBundleToTransforms.get(getPluginId(object));
		String objectXPath = ProductTransformFactory.toXPath(object, true);
		if (transforms != null) {
			for (Iterator i = transforms.iterator(); i.hasNext();) {
				IProductTransform transform = (IProductTransform) i.next();
				if (transform.getXPath().indexOf(objectXPath) != -1)
					return transform;
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.pde.internal.core.iproduct.ITransformationsManager#getAll
	 * (java.lang.String)
	 */
	public IProductTransform[] getAll(String pluginId) {
		IProductTransform[] result = new IProductTransform[0];
		List transforms = (List) fBundleToTransforms.get(pluginId);
		if (transforms != null) {
			result = (IProductTransform[]) transforms.toArray(new IProductTransform[transforms.size()]);
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.eclipse.pde.internal.core.iproduct.IProductObject#parse(org.w3c.dom
	 * .Node)
	 */
	public void parse(Node node) {
		fNotify = false;
		NodeList nodeList = node.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node child = nodeList.item(i);
			if (child.getNodeType() == Node.ELEMENT_NODE) {
				IProductTransform transform = ProductTransformFactory.parse(getModel(), child);
				if (transform != null)
					this.add(transform);

			}
		}
		fNotify = true;
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.pde.core.IWritable#write(java.lang.String,
	 * java.io.PrintWriter)
	 */
	public void write(String indent, PrintWriter writer) {
		for (Iterator i = fBundleToTransforms.entrySet().iterator(); i.hasNext();) {
			List transforms = (List) ((Map.Entry) i.next()).getValue();
			if (transforms == null) // safety check
				continue;
			for (Iterator j = transforms.iterator(); j.hasNext();) {
				IProductTransform entry = (IProductTransform) j.next();
				entry.write(indent, writer);
			}
		}
	}

	private boolean isManagedObject(IPluginObject object) {
		return object != null && (object instanceof IPluginElement || object instanceof IPluginExtension);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.pde.core.IModelChangedListener#modelChanged(org.eclipse.pde.core.IModelChangedEvent)
	 */
	public void modelChanged(IModelChangedEvent event) {
		Object[] changedObjects = event.getChangedObjects();
		if (IModelChangedEvent.REMOVE != event.getChangeType())
			return;
		switch (event.getChangeType()) {
			case IModelChangedEvent.REMOVE :
				for (int i = 0; i < changedObjects.length; ++i) {
					if (changedObjects[i] instanceof IProductPlugin) {
						String pluginId = ((IProductPlugin) changedObjects[i]).getId();
						removeAll(pluginId);
					}
				}
				break;
			case IModelChangedEvent.CHANGE :
				updateObjects(changedObjects);
				break;

		}

	}

	private void updateObjects(Object[] objects) {
		for (int i = 0; i < objects.length; ++i) {
			if (objects[i] instanceof IPluginObject) {
				IProductTransform transform = getTransformFor((IPluginObject) objects[i]);
				if (transform != null) {
					//TODO change it when other transfroms are available
					if (transform instanceof RemoveTransform) {
						((RemoveTransform) transform).setTransformedObject(objects[i]);
					}
				}
			}
		}
	}
}
